import {core_fsm, FSM_CORE_EVENT_CONSTANT, loginProcedure} from "@/fsm/weila_fsm";
import {interpret} from "xstate";

import type {
    WL_CoreInterface,
    WL_ExtEvent,
    WL_LoginParam,
    WL_MsgDataErrorInfo,
    WL_NetworkEvent,
    WL_PbMsgData,
    WL_PbMsgHandler,
    WL_PromiseCallback,
    WL_PttPacket,
    WL_PttPayload,
    WL_PttRecordInd
} from "./weila_datas";
import {
    PttCoreListener,
    WL_ExtEventID,
    WL_NetworkEventID,
    WL_NetworkState,
    WL_PttPackType,
    WL_PttRecordState,
} from "./weila_datas";


import type {
    WL_IDbAudioData,
    WL_IDbFileData,
    WL_IDbLocationInfo,
    WL_IDbLocationShared,
    WL_IDbMsgData,
    WL_IDbPttData,
    WL_IDbServiceSessionInfo,
    WL_IDbSession,
    WL_IDbUserInfo
} from "@/proto/weilapb_external_data";
import {WL_IDbMsgDataStatus, WL_IDbMsgDataType} from "@/proto/weilapb_external_data";

import {WL} from "@/proto/weilapb";
import {getMsgDataIdByCombo, getPbResultCodeMassage} from "@/proto/weilapb_utils";
import type {WLBuildMsgRet} from "@/proto/weilapb_wrapper_data";
import WLSessionModule from "@/main/weila_session_module";
import WLLoginModule from "@/main/weila_login_module";
import WLBusinessModule from "@/main/weila_business_module";

import {TinyEmitter} from "tiny-emitter";
import {Weila_MemDB} from "@/mem_store/weila_db";
import {TextMsgDataParser} from "@/proto/weilapb_textmsg_parser";
import {WeilaPBSessionWrapper} from "@/proto/weilapb_session_wrapper";
import Long from "long";

import debug from "debug";
import WLLocationModule from "@/main/weila_location_module";
import {AliOssHelper} from "@/core/weila_ali_oss";

// @ts-ignore
import NetWorker from "workerize-loader?inline!./weila_network";
import {WL_PttCore} from "@/core/weila_pttcore";


const CLog = debug("CoreLog");

interface WL_BurstInfo {
    sessionId: string;
    sessionType: number;
    dataValid: boolean;
    packetPayloadList: WL_PttPayload[];
    curMarker: WL_PttPackType;
    dbMsgData: WL_IDbMsgData|null;
    dbMsgId: number;
    created: number;
}

interface WL_WaitingRspPbMsgInfo {
    resolve(value: any): any;
    reject(reason: any): any;
    pbMsgData: WL_PbMsgData;
    timeout: number;
}

interface WL_SendingPbMsgInfo {
    resolve(value: any): any;
    reject(reason: any): any;
    buildPbMsg: WLBuildMsgRet;
    timeout?: number;
}

class WeilaCore implements WL_CoreInterface, PttCoreListener {
    private readonly fsmService: any;
    private network: any;
    private curBurstInfo: WL_BurstInfo|undefined;
    private pttCore: WL_PttCore|undefined;

    private sessionModule: WLSessionModule|undefined;
    private loginModule: WLLoginModule|undefined;
    private locationModule: WLLocationModule|undefined;
    private businessModule: WLBusinessModule|undefined;

    private connectCb: WL_PromiseCallback|undefined;
    private pbMsgHandlerList: Map<number, WL_PbMsgHandler>;
    private waitingRspMap: Map<number, WL_WaitingRspPbMsgInfo>;
    private sendingPbMsgList: WL_SendingPbMsgInfo[];
    private waitingTimeoutTimer: any;
    private heartbeatTimerId: any;
    private refreshTokenTimerId: any;
    private loginParam: WL_LoginParam|undefined;
    private static readonly MaxLoginRetryTimes: number = 50;

    private isConnected: boolean;
    private emitter: TinyEmitter|undefined;
    private isLogined: boolean;

    constructor() {
        /**
         * 初始化变量
         */
        this.emitter = new TinyEmitter();
        this.curBurstInfo = undefined;
        this.connectCb = undefined;
        this.isConnected = false;
        this.waitingTimeoutTimer = undefined;
        this.pbMsgHandlerList = new Map<number, WL_PbMsgHandler>();
        this.waitingRspMap = new Map<number, WL_WaitingRspPbMsgInfo>();
        this.sendingPbMsgList = [];
        this.isLogined = false;

        const login_service = loginProcedure.withContext({
            loginParam: undefined,
            loginUserInfo: undefined,
            retryTimes: 0
        }).withConfig({
            actions: {
                onDisconnectedEntry: this.onDisconnectedEntry.bind(this),
                onConnectingEntry: this.onConnectingEntry.bind(this),
                onConnectedEntry: this.onConnectedEntry.bind(this),
                onLoginServiceFail: this.onLoginServiceFail.bind(this),
                onLoginedEntry: this.onLoginedEntry.bind(this),
                onLoginFailEntry: this.onLoginFailEntry.bind(this),
                onLoginUinitedEntry: this.onLoginUinitedEntry.bind(this),
            },
            services: {
                connect: this.connect.bind(this),
                login: this.login.bind(this)
            },
            guards: {
                tryEnough: this.tryEnough.bind(this),
                shouldTryLogin: this.shouldTryLogin.bind(this)
            }
        })

        const core_service = core_fsm.withContext({
            core: this
        }).withConfig({
            actions: {
                onUinitEntry: this.onUinitEntry.bind(this),
                onInitEvt: this.onInitEvt.bind(this),
                onInitedEntry: this.onInitedEntry.bind(this),
                onLoginingSucc: this.onLoginingSucc.bind(this),
                onLoginingFail: this.onLoginingFail.bind(this),
                onReadyEntry: this.onReadyEntry.bind(this),
                onReadyExit: this.onReadyExit.bind(this),
                onLogoutEvent: this.onLogoutEvent.bind(this),
                onReLoginAction: this.onReLoginAction.bind(this),
                onDisconnectWhileReady: this.onDisconnectWhileReady.bind(this)
            },

            services: {
                LoginProcedure: login_service,
                prepare: this.prepare.bind(this)
            },

            guards: {
                isSucc: this.isSucc.bind(this),
                isLogout: this.isLogout.bind(this)
            }
        });

        this.fsmService = interpret(core_service).onTransition((state: any, event: any) => {
            CLog("事件: %o 从状态: %o ===> 状态: %o", event, state.history ? state.history.value: '初始状态', state.value);
        }).start();
    }

    public init() {
        this.fsmService.send(FSM_CORE_EVENT_CONSTANT.FSM_CORE_INIT_EVT);
    }

    private onNetworkMessage(event: any) {
        const netEvent = event.data as WL_NetworkEvent;
        switch (netEvent.eventId) {
            case WL_NetworkEventID.NET_STATE_IND_EVT: {
                const netState = netEvent.eventData.state as WL_NetworkState;
                CLog("network state: %o", netState)
                if (netState == WL_NetworkState.NET_CONNECTED_STATE) {
                    if (this.connectCb) {
                        this.connectCb.resolve(true);
                        this.connectCb = undefined;
                    }

                    this.isConnected = true;
                } else if (netState == WL_NetworkState.NET_DISCONNECTED_STATE) {
                    if (this.connectCb) {
                        this.connectCb.reject("disconnected");
                        this.connectCb = undefined;
                    }


                    this.waitingRspMap.forEach((value, key) => {
                        value.reject(new Error('网络失去连接'));
                        this.waitingRspMap.delete(key);
                    })


                    this.isConnected = false;
                    this.fsmService.send(FSM_CORE_EVENT_CONSTANT.FSM_CORE_NET_PROBLEM_EVT);
                    //TODO: 通知外部，网络断开
                }else if (netState == WL_NetworkState.NET_DISCONNECTING_STATE) {
                    CLog("Disconnecting %o", `code: ${netEvent.eventData.code} reason: ${netEvent.eventData.reason}`);
                }
            }
            break;

            case WL_NetworkEventID.NET_MSG_RECV_IND_EVT: {
                const msgData = netEvent.eventData as WL_PbMsgData;
                const serverMessage = WL.Service.ServiceMessage.decode(msgData.pbMsgData);

                if (this.pbMsgHandlerList.has(serverMessage.serviceHead!.serviceId as number)) {
                    const handler = this.pbMsgHandlerList.get(serverMessage.serviceHead!.serviceId as number)!;
                    handler(serverMessage);
                }
            }
            break;

            case WL_NetworkEventID.NET_EXCEPTION_IND_EVT: {
                if (this.connectCb) {
                    this.connectCb.reject(netEvent.eventData);
                    this.connectCb = undefined;
                }

                // 通知外部，网络异常
                this.sendExtEvent(WL_ExtEventID.WL_EXT_NET_EXCEPTION_IND, netEvent.eventData);
            }
            break;
        }
    }

    private sendNetworkEvent(eventId: WL_NetworkEventID, eventData: any) {
        const netEvent = {} as WL_NetworkEvent;
        CLog("sendNetworkEvent", eventId);
        netEvent.eventId = eventId;
        netEvent.eventData = eventData;
        this.network!.postMessage(netEvent);
    }

    private processSendingPbMsg(): void {
        //CLog("处理PB消息，队列有 %d 个等待发送消息, 等待检测中?: %o", this.sendingPbMsgList.length, this.waitingTimeoutTimer)
        if (this.sendingPbMsgList.length > 0) {
            const sendingItem = this.sendingPbMsgList.shift()!;
            const waitingItem = {} as WL_WaitingRspPbMsgInfo;
            waitingItem.resolve = sendingItem.resolve;
            waitingItem.reject = sendingItem.reject;
            waitingItem.pbMsgData = {} as WL_PbMsgData;
            waitingItem.pbMsgData.header = sendingItem.buildPbMsg.weilaMsgHeader!;
            waitingItem.pbMsgData.pbMsgData = sendingItem.buildPbMsg.reqData;
            waitingItem.timeout = new Date().getTime() + sendingItem.timeout!;
            this.waitingRspMap.set(sendingItem.buildPbMsg.weilaMsgHeader!.seqNum, waitingItem);
            this.sendNetworkEvent(WL_NetworkEventID.NET_SEND_DATA_EVT, sendingItem.buildPbMsg.reqData);
        }

        if (this.sendingPbMsgList.length > 0) {
            setTimeout(this.processSendingPbMsg.bind(this), 0);
        }

        if (this.waitingTimeoutTimer === undefined) {
            this.waitingTimeoutTimer = setInterval(this.sysTimeCheck.bind(this), 1000);
        }
    }

    private sysTimeCheck() {
        //CLog('--------->onSysTimeChecking %o %o', this.waitingRspMap.size, this.waitingTimeoutTimer);
        if (this.waitingRspMap.size > 0) {
            const timeoutKeyList: any[] = [];
            const now = new Date().getTime();

            this.waitingRspMap.forEach((value, key) => {
                if (value.timeout <= now) {
                    timeoutKeyList.push(key);
                }
            });

            timeoutKeyList.forEach(key => {
                let waitingPbMsg = this.waitingRspMap.get(key);
                CLog("发送消息超时:%o", key);
                if (waitingPbMsg!.reject !== undefined) {
                    waitingPbMsg!.reject(new Error('消息超时'));
                }
                this.waitingRspMap.delete(key);
            });

            if (this.waitingRspMap.size > 0) {
                return;
            }
        }

        if (this.waitingTimeoutTimer !== undefined) {
            clearInterval(this.waitingTimeoutTimer);
            this.waitingTimeoutTimer = undefined;
        }
    }

    executeCoreFunc(funcName: string, ...argvs: any[]): Promise<any> {
        if ((this as any)[funcName] && typeof (this as any)[funcName] === 'function') {
            return (this as any)[funcName](...argvs);
        }

        return Promise.reject(new Error('Not Support Function:' + funcName));
    }

    public registerPbMsgHandler(serviceId: number, handler: (data: any) => void): void {
        this.pbMsgHandlerList.set(serviceId, handler);
    }

    public rspPbMsg(seq: number, resultCode: number, data: any): void {
        if (this.waitingRspMap.has(seq)) {
            const sendingItem = this.waitingRspMap.get(seq);
            this.waitingRspMap.delete(seq);

            if (sendingItem) {
                if (resultCode == 0) {
                    sendingItem.resolve(data);
                }else {
                    let errorMessage = "RET_CODE:" + resultCode + "-MESSAGE:";
                    const msg = getPbResultCodeMassage(resultCode);
                    errorMessage += msg ? msg:'未知错误码';
                    errorMessage += ":END";

                    const errInfo = {} as WL_MsgDataErrorInfo;
                    errInfo.errCode = resultCode;
                    errInfo.errMsg = msg ? msg:'未知错误码';
                    sendingItem.reject(errInfo);
                }
            }
        }
    }

    public sendExtEvent(eventId: WL_ExtEventID, data: any): void {
        const event = {} as WL_ExtEvent;
        event.event = eventId;
        event.data = data;
        this.emitter!.emit("ext-event", event);

        if (eventId === WL_ExtEventID.WL_EXT_ACCOUNT_WAS_KICKED_OUT) {
            this.loginParam = undefined;
        }
    }

    public weila_onEvent(eventHandler: (eventId: WL_ExtEventID, eventData: any) => void) {
        this.emitter!.on('ext-event', (event: WL_ExtEvent) => {
            if (eventHandler) {
                eventHandler(event.event, event.data);
            }
        })
    }

    public sendPbMsg(buildPbMsg: WLBuildMsgRet, timeout?: number, loginMsg?: boolean): Promise<any> {
        if (!this.isLogined && (loginMsg === undefined || !loginMsg)) {
            CLog("send PbMsg fail, because %o %o", this.isLogined, loginMsg);
            return Promise.reject("not ready");
        }

        return new Promise<any>((resolve, reject) => {
            const sendingItem = {} as WL_SendingPbMsgInfo;
            sendingItem.resolve = resolve;
            sendingItem.reject = reject;
            sendingItem.buildPbMsg = buildPbMsg;
            sendingItem.timeout = timeout ? timeout: 6000;
            this.sendingPbMsgList.push(sendingItem);
            setTimeout(this.processSendingPbMsg.bind(this), 0);
        });
    }

    private onDisconnectedEntry(context: any, action: any, actionMeta: any) {
        CLog("onDisconnectedEntry %o %o", context, action);
	}

    private onConnectingEntry(context: any, action: any, actionMeta: any) {
        CLog("onConnectingEntry %o %o", context, action);
	}

    private onLoginServiceFail(context: any, action: any, actionMeta: any) {
        CLog("onLoginServiceFail %o %o", context, action);
    }

    private onConnectedEntry(context: any, action: any, actionMeta: any) {
        CLog("onConnectedEntry %o %o", context, action);
	}

    private onLoginedEntry(context: any, action: any, actionMeta: any) {
        context.loginUserInfo = actionMeta._event.data.data;
        CLog("onLoginedEntry %o %o, loginUserInfo:%o", context, actionMeta, context.loginUserInfo);
	}

    private onLoginFailEntry(context: any, action: any, actionMeta: any) {
        CLog("onLoginFailEntry %o %o", context, actionMeta);
    }

    private onLoginUinitedEntry(context: any, action: any, actionMeta: any) {
        CLog("onLoginUinitedEntry %o %o", context, actionMeta);
    }

    private onLoginFailedEntry(context: any, action: any, actionMeta: any) {
        CLog("onLoginFailedEntry %o %o", context, actionMeta);
	}

    private onLoginFailed(context: any, action: any, actionMeta: any) {
        CLog("onLoginFailed %o %o", context, action);
	}

    private onUinitEntry(context: any, action: any, actionMeta: any) {
        CLog("onUinitEntry %o %o", context, action);
	}

    private onInitEvt(context: any, action: any, actionMeta: any) {
        /**
         * 初始化网络任务
         */
        //this.network = new Worker(/* webpackChunkName: "weila_network" */ new URL('@/core/weila_network.ts', import.meta.url));
        this.network = new NetWorker();
        this.network.onmessage = this.onNetworkMessage.bind(this);

        /**
         * 初始化协议模块
         */
        this.sessionModule = new WLSessionModule(this);
        this.loginModule = new WLLoginModule(this);
        this.locationModule = new WLLocationModule(this);
        this.businessModule = new WLBusinessModule(this);

        /**
         * 初始化ptt模块
         */
        this.pttCore = new WL_PttCore(this.sessionModule);
        this.pttCore.init();
        this.pttCore.setListener(this);
        this.sessionModule.setPttCore(this.pttCore);
	}

    private onDisconnectWhileReady(context: any, action: any, actionMeta: any) {
        this.sendExtEvent(WL_ExtEventID.WL_EXT_NET_DISCONNECTED_IND, null);
    }

    private onReLoginAction(context: any, action: any, actionMeta: any) {
        if (this.loginParam) {
            this.fsmService.send(FSM_CORE_EVENT_CONSTANT.FSM_CORE_LOGIN_EVT, {
                data: {
                    account: this.loginParam.account,
                    password: this.loginParam.password,
                    countryCode: this.loginParam.countryCode,
                    md5: this.loginParam.md5
                }
            });
        }
    }

    private onLogoutEvent(context: any, action: any, actionMeta: any) {
        this.loginParam = undefined;
        this.waitingRspMap.clear();
        this.pbMsgHandlerList.clear();
        this.sendingPbMsgList = [];

        if (this.waitingTimeoutTimer !== undefined) {
            clearInterval(this.waitingTimeoutTimer);
            this.waitingTimeoutTimer = undefined;
        }
        this.sendNetworkEvent(WL_NetworkEventID.NET_DISCONNECT_EVT, undefined);
    }

    private onReadyExit(context: any, action: any, actionMeta: any) {
        clearInterval(this.heartbeatTimerId);
        this.heartbeatTimerId = null;

        clearInterval(this.refreshTokenTimerId);
        this.refreshTokenTimerId = null;
        this.isLogined = false;
    }

    private onReadyEntry(context: any, action: any, actionMeta: any) {
        /**
         * 当登录成功，并且做了准备动作后，则进入准备状态。在此状态开启需要的定时器，
         * 心跳定时器，token刷新定时器
         */
        CLog("onReadyEntry %o", actionMeta._event)
        if (this.loginParam && this.loginParam.callback) {
            if (this.isLogined) {
                this.loginParam.callback.resolve(action.data.loginUserInfo);
                this.loginParam.callback = undefined;
            }
        }

        this.heartbeatTimerId = setInterval(() => {
            this.loginModule!.sendHeartbeat().then(value => {
                CLog('发送心跳成功');
            }).catch(reason => {
                CLog('发送心跳失败 %o', reason);
            });
        }, 30000);

        this.refreshTokenTimerId = setInterval(() => {
            this.loginModule!.refreshToken().then(value => {
                CLog('刷新token成功');
            }).catch(reason => {
                CLog('刷新token失败: %o', reason);
            })
        }, 3600000);

        this.sysTimeCheck();
	}

    private onInitedEntry(context: any, action: any, actionMeta: any) {
        CLog("onInitedEntry %o %o", context, action, actionMeta);
	}

    private onLoginingSucc(context: any, action: any, actionMeta: any) {
        CLog("onLoginingSucc %o %o", context, action);
        this.isLogined = true;
    }

    private onLoginingFail(context: any, action: any, actionMeta: any) {
        CLog("onLoginingFail %o %o", context, action);
        if (this.loginParam && this.loginParam.callback) {
            this.loginParam.callback.reject(action.data.reason);
            this.loginParam.callback = undefined;
            this.loginParam = undefined;
        }else {
            this.sendExtEvent(WL_ExtEventID.WL_EXT_NET_LOGIN_FAILED_IND, action.data.reason);
        }
    }

    private connect(context: any, action: any): Promise<boolean> {
        return new Promise<boolean>((resolve, reject) => {
            this.connectCb = {} as WL_PromiseCallback;
            this.connectCb.resolve = resolve;
            this.connectCb.reject = reject;
            this.sendNetworkEvent(WL_NetworkEventID.NET_CONNECT_EVT, null);
        })
    }

    private login(context: any, action: any): Promise<WL_IDbUserInfo> {
        CLog("login %o %o", context, this.loginParam);
        const loginParam = context.loginParam;
        return this.loginModule!.loginReq(loginParam.account, loginParam.password, loginParam.countryCode, loginParam.md5);
    }

    private async prepare(context: any, action: any): Promise<WL_IDbUserInfo> {
        /**
         * 当登录成功后，需要初始化会话，初始化服务
         */
        try {
            await this.sessionModule!.initSessions();
            await this.businessModule!.initService(this.sessionModule!.getSessionList());
            return action.data;
        }catch (e) {
            return Promise.reject("准备期间出错错误:" + e);
        }
    }

    private tryEnough(context: any, action: any, condMeta: any): boolean {
        CLog("tryEnough %o %o %o", context, action, condMeta);
        if (context.retryTimes < WeilaCore.MaxLoginRetryTimes) {
            context.retryTimes++;
            return false;
        }

        return true;
    }

    private shouldTryLogin(context: any, action: any, condMeta: any): boolean {
        CLog("shouldTryLogin %o %o %o", context, action, condMeta);

        const re =  /errCode.*:([0-9]*),.*errMsg.*:(.*)}/;
        if (typeof action.data == 'string') {
            const m = action.data.match(re);
            if (m !== null) {
                const errCode = m[1];

                CLog("match result is %o", m);

                if (errCode == WL.Service.ResultCode.RESULT_LOGIN_ACCOUNT_OR_PASSWD_ERROR ||
                    errCode == WL.Service.ResultCode.RESULT_LOGIN_PASSWORD_ERROR_TOO_TIMES ||
                    errCode == WL.Service.ResultCode.RESULT_LOGIN_FORBID_USER) {
                    return false;
                }
            }
        }

        return true;
    }

    private isSucc(context: any, action: any, condMeta: any): boolean {
        CLog("isSucc %o", action.data.result);
        return action.data.result;
    }

    private isLogout(context: any, action: any, condMeta: any): boolean {
        CLog("isLogout %o %o", action, condMeta);
        return this.loginParam === undefined
    }

    public onAudioItemPlayEnd(msgData: WL_IDbMsgData): void {
        this.sendExtEvent(WL_ExtEventID.WL_EXT_AUDIO_ITEM_PLAY_END_IND, msgData);
    }

    public onPlayAudioData(msgData: WL_IDbMsgData, frameCount: number): void {
        if (frameCount != -1) {
            this.sendExtEvent(WL_ExtEventID.WL_EXT_AUDIO_DATA_PLAY_PROGRESS_IND, {msgData, frameCount});
        }else {
            this.sendExtEvent(WL_ExtEventID.WL_EXT_AUDIO_DATA_PLAY_END_IND, {msgData, frameCount});
        }
    }

    public onCurrentPlayAudioMsg(msgData: WL_IDbMsgData): void {
        this.sendExtEvent(WL_ExtEventID.WL_EXT_CURRENT_PLAY_AUDIO_IND, msgData);
    }

    public onRecordPttPacketInd(pttPacket: WL_PttPacket): void {
        const pttData = {} as WL_IDbPttData;
        const loginUserInfo = Weila_MemDB.getInstance().getLoginUserInfo();

        if (this.curBurstInfo === undefined) {
            return;
        }

        pttData.seq = pttPacket.seq;
        pttData.data = pttPacket.data;
        pttData.frameCount = pttPacket.frameCount;
        pttData.seqInPackage = pttPacket.seqInPackage;
        pttData.mark = pttPacket.mark;
        pttData.sourceType = WL.Session.AudioSourceType.PTT_SOURCE_APP;

        if (pttPacket.mark == WL_PttPackType.PTT_FIRST_PACK || pttPacket.mark == WL_PttPackType.PTT_WHOLE_PACK) {
            let seq = Weila_MemDB.getInstance().getMsgSeq();
            // 因为要获得最后一条消息的id，所以需要缓存最后一条消息的msgId。
            const lastMsgId = Weila_MemDB.getInstance().getLastMsgId(this.curBurstInfo!.sessionId, this.curBurstInfo!.sessionType);
            this.curBurstInfo.dbMsgData = {} as WL_IDbMsgData;
            this.curBurstInfo.dbMsgData.audioData = {} as WL_IDbAudioData;
            this.curBurstInfo.dbMsgData.audioData.data = pttData.data.slice(0);
            this.curBurstInfo.dbMsgData.audioData.frameCount = pttData.frameCount;
            this.curBurstInfo.dbMsgData.msgType = WL_IDbMsgDataType.WL_DB_MSG_DATA_AUDIO_TYPE;
            this.curBurstInfo.dbMsgData.senderId = loginUserInfo!.userId;
            this.curBurstInfo.dbMsgData.sessionId = this.curBurstInfo.sessionId;
            this.curBurstInfo.dbMsgData.sessionType = this.curBurstInfo.sessionType;
            this.curBurstInfo.dbMsgData.autoReply = 0;
            this.curBurstInfo.dbMsgData.msgId = lastMsgId;
            this.curBurstInfo.dbMsgData.combo_id = getMsgDataIdByCombo(this.curBurstInfo.dbMsgData, seq++);
        }else {
            if (this.curBurstInfo.dbMsgData) {
                const newData =
                    new Uint8Array(this.curBurstInfo.dbMsgData.audioData!.data!.length + pttPacket.data.length);
                newData.set(this.curBurstInfo.dbMsgData.audioData!.data!);
                newData.set(pttPacket.data, this.curBurstInfo.dbMsgData.audioData!.data!.length);
                this.curBurstInfo.dbMsgData.audioData!.frameCount += pttPacket.frameCount;
                this.curBurstInfo.dbMsgData.audioData!.data = newData;
            }else {
                return;
            }
        }

        const buildMsgRet = WeilaPBSessionWrapper.buildPttSessionMsgReq(
            Long.fromValue(this.curBurstInfo.sessionId),
            this.curBurstInfo.sessionType,
            loginUserInfo!.userId,
            false,
            pttData);

        let error: any = undefined;

        if (buildMsgRet.resultCode == 0) {
            this.sendPbMsg(buildMsgRet).then(value => {
                const rsp = value as WL.Session.IRspMsg;
                if (this.curBurstInfo !== undefined) {
                    this.curBurstInfo.dbMsgData!.msgId = rsp.msgId || 0;
                    this.curBurstInfo.dbMsgData!.created = Long.fromValue(rsp.created || 0).toNumber();
                    this.curBurstInfo.dbMsgData!.status = WL_IDbMsgDataStatus.WL_DB_MSG_DATA_STATUS_SENT;
                }
            }).catch(reason => {
                if (this.curBurstInfo !== undefined) {
                    this.curBurstInfo.dbMsgData!.msgId = -1;
                    this.curBurstInfo.dbMsgData!.created = new Date().getTime() / 1000;
                    this.curBurstInfo.dbMsgData!.status = WL_IDbMsgDataStatus.WL_DB_MSG_DATA_STATUS_UNSENT;
                }
                error = reason;
            }).finally(() => {
                if (this.curBurstInfo !== undefined) {

                    //依据marker状态发送不同的对外信息。
                    if (this.curBurstInfo.dbMsgData!.msgId != -1) {
                        const sessionInfo = Weila_MemDB.getInstance().getSession(this.curBurstInfo.sessionId, this.curBurstInfo.sessionType);
                        if (sessionInfo) {
                            sessionInfo.lastMsgId = this.curBurstInfo.dbMsgData!.msgId;
                            Weila_MemDB.getInstance().putSession(sessionInfo);
                        }
                    }

                    const pttRecordInd: WL_PttRecordInd = {} as WL_PttRecordInd;
                    pttRecordInd.msgData = this.curBurstInfo.dbMsgData!;
                    CLog("status: %o", this.curBurstInfo.dbMsgData!.status);
                    if (this.curBurstInfo.dbMsgData!.status == WL_IDbMsgDataStatus.WL_DB_MSG_DATA_STATUS_SENT) {
                        if (pttPacket.mark == WL_PttPackType.PTT_INTER_PACK) {
                            pttRecordInd.state = WL_PttRecordState.PTT_AUDIO_RECORDING;
                        } else if (pttPacket.mark == WL_PttPackType.PTT_END_PACK ||
                            pttPacket.mark == WL_PttPackType.PTT_WHOLE_PACK) {

                            Weila_MemDB.getInstance().delMsgData(this.curBurstInfo.dbMsgData!.combo_id);
                            this.curBurstInfo.dbMsgData!.combo_id = getMsgDataIdByCombo(this.curBurstInfo.dbMsgData!, 0);
                            Weila_MemDB.getInstance().putMsgData(this.curBurstInfo.dbMsgData!);

                            pttRecordInd.state = WL_PttRecordState.PTT_AUDIO_RECORDING_END;
                            this.sendExtEvent(WL_ExtEventID.WL_EXT_MSG_SEND_IND, this.curBurstInfo.dbMsgData);
                        } else {
                            pttRecordInd.state = WL_PttRecordState.PTT_AUDIO_RECORDING_START;
                        }
                    } else {
                        this.weila_releaseTalk(error);
                        pttRecordInd.state = WL_PttRecordState.PTT_AUDIO_RECORDING_END;
                        this.sendExtEvent(WL_ExtEventID.WL_EXT_MSG_SEND_IND, this.curBurstInfo.dbMsgData);
                    }

                    this.sendExtEvent(WL_ExtEventID.WL_EXT_PTT_RECORD_IND, pttRecordInd);

                    if (pttPacket.mark == WL_PttPackType.PTT_END_PACK || pttPacket.mark == WL_PttPackType.PTT_WHOLE_PACK) {
                        this.curBurstInfo = undefined;
                    }
                }
            })
        }
    }

    onRecorderErrorInd(error: string): void {
        this.weila_releaseTalk(error);
        console.log("recorder error:", error);
    }

    /**
     * 登出平台，如果网页退出，最好是能调用以下这个登出过程
     */
    public async weila_logout(): Promise<boolean> {
        try {
            await this.loginModule!.logoutReq();
        }finally {
            this.fsmService.send(FSM_CORE_EVENT_CONSTANT.FSM_CORE_LOGOUT_EVT);
        }
        return true;
    }

    /**
     * 登录账号
     * @param account  手机号或微喇号
     * @param password 密码
     * @param countryCode 如果手机号则为"86"，如果是微喇号，则是"0"
     * @param md5 如果密码是MD5，这里填True。如果是明文，则填False
     *
     * 返回Promise，内容是WL_IDbUserInfo
     */

    public weila_login(account: string, password: string, countryCode: string, md5: boolean): Promise<WL_IDbUserInfo> {
        return new Promise<WL_IDbUserInfo>((resolve, reject) => {
            if (!this.loginParam) {
                this.loginParam = {} as WL_LoginParam;
                this.loginParam.retryCount = 0;
                this.loginParam.callback = {} as WL_PromiseCallback;
                this.loginParam.callback.resolve = resolve;
                this.loginParam.callback.reject = reject;
                this.loginParam.account = account;
                this.loginParam.password = password;
                this.loginParam.md5 = md5;
                this.loginParam.countryCode = countryCode;
            }

            this.fsmService.send(FSM_CORE_EVENT_CONSTANT.FSM_CORE_LOGIN_EVT, {
                data: {
                    account: account,
                    password: password,
                    countryCode: countryCode,
                    md5: md5
                }
            });
        });
    }

    /**
     * 发起实时语音对讲
     * @param sessionId 会话ID
     * @param sessionType 会话类型
     *
     * 返回Promise，内容是boolean代表成功与否
     */
    public async weila_requestTalk(sessionId: string, sessionType: number): Promise<boolean> {
        try {
            await this.pttCore!.requestTalk();
        }catch (e) {
            return Promise.reject("对讲出错" + e);
        }

        this.curBurstInfo = {} as WL_BurstInfo;
        this.curBurstInfo.curMarker = WL_PttPackType.PTT_FIRST_PACK;
        this.curBurstInfo.packetPayloadList = [];
        this.curBurstInfo.sessionId = sessionId;
        this.curBurstInfo.sessionType = sessionType;
        this.curBurstInfo.dbMsgData = null;
        this.curBurstInfo.dbMsgId = 0;
        return true;
    }

    /**
     * 停止实时对讲
     * @param error 如果有错误发生，则error可以填入内容
     */
    public weila_releaseTalk(error: any): void {
        this.pttCore!.releaseTalk(error);
    }

    /**
     * 播放一条语音消息, 如果再播放一次，会把之前的播放停止
     * @param msgData 语音类型的消息
     */
    public async weila_playSingle(msgData: WL_IDbMsgData): Promise<boolean> {
        return this.pttCore!.playSingleAudio(msgData);
    }

    /**
     * 停止播放单条语音消息
     */
    public weila_stopSingle(): boolean {
        return this.pttCore!.stopSingleAudio()
    }

    /**
     * 如果有多条的实时语音，可以跳过当前的，去播放下一条的。
     */
    public weila_nextStreamAudio() {
        return this.pttCore!.playNextStreamAudioItem();
    }

    /**
     * 开启一个新的会话(主要的作用是为了开辟一个session项目存于内存）
     * @param sessionId 会话id
     * @param sessionType 会话类型
     * @param extra 特殊信息JSON
     */
    public weila_startSession(sessionId: string, sessionType: number, extra?: any): WL_IDbSession {
        return this.sessionModule!.startSession(sessionId, sessionType, extra);
    }

    /**
     * 删除服务器内指定的会话
     * @param sessionId
     * @param sessionType
     */
    public weila_deleteSession(sessionId: string, sessionType: number): Promise<boolean> {
        return this.sessionModule!.deleteSession(sessionId, sessionType);
    }

    /**
     * 获取服务号的会话，依据sessionID
     * @param sessionId 由服务号id和客户id合并创建的
     */
    public async weila_getServiceSession(sessionId: string): Promise<WL_IDbServiceSessionInfo|undefined> {
         return this.businessModule!.getSessionServiceFromServer(sessionId);
    }

    /**
     * 获取最近从msgId往后的count条消息
     * @param sessionId 会话id
     * @param sessionType 会话类型
     * @param fromMsgId 最新的消息id
     * @param count 数目
     *
     * 返回WL_IDbMsgData的数组，不一定对应count的数目，只会<=count
     */
    public async weila_getLatestMsgData(sessionId: string, sessionType: number, fromMsgId: number, count: number): Promise<WL_IDbMsgData[]> {
        return this.sessionModule!.getMsgDataFromServer(sessionId, sessionType, fromMsgId, count);
    }

    /**
     * 发送文字消息
     * @param sessionId 会话id
     * @param sessionType 会话类型
     * @param text 文字
     * @param location 可以不管
     */
    public async weila_sendTextMsg(sessionId: string, sessionType: number, text: string, location?: WL_IDbLocationInfo): Promise<boolean> {
        return this.sessionModule!.sendMsgData(sessionId, sessionType, text, WL_IDbMsgDataType.WL_DB_MSG_DATA_TEXT_TYPE, undefined, location);
    }

    /**
     * 返回对应的会话信息
     * @param sessionId 会话id
     * @param sessionType 会话类型
     *
     * 返回undefined或会话信息
     */
    public weila_getSession(sessionId: string, sessionType: number): WL_IDbSession|undefined {
        return Weila_MemDB.getInstance().getSession(sessionId, sessionType);
    }

    /**
     * 获取对应的用户信息，这些信息是存于内存数据库中，不一定能获取得到
     * @param userId
     */
    public weila_getUserInfo(userId: number): WL_IDbUserInfo|undefined {
        return Weila_MemDB.getInstance().getUser(userId);
    }

    private async weila_sendFileInfo(sessionId: string, sessionType: number,
                                     filename: string, dataUrl: string,
                                     dataLength: number, msgType: WL_IDbMsgDataType): Promise<boolean> {
        const lastMsgId = Weila_MemDB.getInstance().getLastMsgId(sessionId, sessionType);
        const msgData: WL_IDbMsgData = {} as WL_IDbMsgData;
        let seq = Weila_MemDB.getInstance().getMsgSeq();
        const loginUserInfo = Weila_MemDB.getInstance().getLoginUserInfo();

        msgData.fileInfo = {} as WL_IDbFileData;
        msgData.fileInfo.fileName = filename;
        msgData.fileInfo.fileThumbnail = TextMsgDataParser.getExtendFileFormatUrl(msgData.fileInfo.fileName);
        msgData.fileInfo.fileSize = dataLength;
        msgData.msgType = msgType;
        msgData.msgId = lastMsgId;
        msgData.sessionType = sessionType;
        msgData.sessionId = sessionId;
        msgData.status = WL_IDbMsgDataStatus.WL_DB_MSG_DATA_STATUS_SENDING;
        msgData.senderId = loginUserInfo!.userId;
        msgData.created = new Date().getTime() / 1000;

        msgData.autoReply = 0;
        msgData.combo_id = getMsgDataIdByCombo(msgData, seq++);
        msgData.preCombo_id = msgData.combo_id;
        msgData.fileInfo.fileUrl = dataUrl;

        Weila_MemDB.getInstance().putMsgSeq(seq);
        Weila_MemDB.getInstance().putMsgData(msgData);

        return this.sessionModule!.sendMsgData(sessionId, sessionType, msgData.fileInfo, msgType, msgData)
    }

    /**
     * 发送图片消息，主要是发送图片URL，图片首先必须先上传图片，存于服务器后，获得URL后再发送
     * @param sessionId 会话id
     * @param sessionType 会话类型
     * @param imageUrl 图片URL
     * @param imageSize 图片大小
     */
    public async weila_sendImage(sessionId: string, sessionType: number, imageUrl: string, imageSize: number): Promise<boolean> {
        return this.weila_sendFileInfo(sessionId, sessionType, "image", imageUrl, imageSize, WL_IDbMsgDataType.WL_DB_MSG_DATA_IMAGE_TYPE);
    }

    /**
     * 发送分享位置信息，包含坐标，地址，以及地图位置的快照图
     * @param sessionId 会话id
     * @param sessionType 会话类型
     * @param position 分享的位置信息
     */
    public async weila_sendPosition(sessionId: string, sessionType: number, position: WL_IDbLocationShared): Promise<boolean> {
         return this.sessionModule!.sendMsgData(sessionId, sessionType, position, WL_IDbMsgDataType.WL_DB_MSG_DATA_LOCATION_TYPE, undefined);
    }

    /**
     * 获取对应会话里，分享位置的用户的位置信息
     * @param sessionId 会话id
     * @param sessionType 会话类型
     *
     * 返回分享位置的用户的位置数组
     */
    public async weila_getSessionLocation(sessionId: string, sessionType: number): Promise<WL_IDbLocationInfo[]> {
        return this.locationModule!.getLocation(sessionId, sessionType);
    }

    /**
     * 重新发送。当消息发送失败（可能因为网络或别的问题），可以重新调用此接口来发送
     * @param msgData
     */
    public weila_reSendMsgData(msgData: WL_IDbMsgData): Promise<boolean> {
        return this.sessionModule!.reSendMsgData(msgData);
    }

    /**
     * 更新内存数据库中的消息
     * @param msgData
     */
    public weila_updateMsgData(msgData: WL_IDbMsgData) {
        Weila_MemDB.getInstance().putMsgData(msgData);
    }

    /**
     * 集合上传和发送，可以发图片和文件，请填对消息类型，图片类型
     * @param sessionId
     * @param sessionType
     * @param filename
     * @param data
     * @param msgType
     */
    public async weila_sendFiles(sessionId: string, sessionType: number, filename: string, data: File, msgType: WL_IDbMsgDataType): Promise<boolean> {
        try {
            const url = await this.weila_uploadFile(sessionId, sessionType, filename, data);
            if (msgType == WL_IDbMsgDataType.WL_DB_MSG_DATA_IMAGE_TYPE) {
                await this.weila_sendImage(sessionId, sessionType, url, data.length);
            }else if (msgType == WL_IDbMsgDataType.WL_DB_MSG_DATA_FILE_TYPE) {
                await this.weila_sendFileInfo(sessionId, sessionType, filename, url, data.length, msgType);
            }else {
                return Promise.reject(new Error("消息类型错误"));
            }
        }catch (e) {
            return Promise.reject(new Error("发送错误:[" + e + "]"));
        }

        return true;
    }


    /**
     * 上传文件或图片或视频到微喇的服务器中
     * @param sessionId 会话id
     * @param sessionType 会话类型
     * @param filename 文件名
     * @param data 文件(File类型)
     *
     * 返回的是上传后得到的URL
     */
    public async weila_uploadFile(sessionId: string, sessionType: number, filename: string, data: File): Promise<string> {
        const loginUserInfo = Weila_MemDB.getInstance().getLoginUserInfo();
        try {
            const uploadRet = await AliOssHelper.getInstance().uploadToCache(loginUserInfo!.userId, sessionId, sessionType, filename, data);
            if (uploadRet.statusCode == 200) {
                return uploadRet.remoteUrl;
            }

            return Promise.reject("上传失败" + uploadRet.statusMessage);
        }catch (e) {
            return Promise.reject("上传失败" + e);
        }
    }

    /**
     * 关闭服务会话
     * @param sessionId
     */
    public weila_closeServiceSession(sessionId: string): Promise<boolean> {
        return this.businessModule!.closeServiceSession(sessionId);
    }

    /**
     * 获取登录的Token
     */
    public weila_getLoginToken(): string {
        return Weila_MemDB.getInstance().getLoginToken();
    }

    /**
     * 获取用户对应的所有会话列表
     */
    public weila_getSessions(): WL_IDbSession[] {
        return this.sessionModule!.getSessionList();
    }

    /**
     * 使指定的会话id静音
     * @param sessionId
     * @param mute
     */
    public weila_muteSession(sessionId: string, mute: boolean): void {
        this.sessionModule!.muteSession(sessionId, mute);
    }

    /**
     * 使所有会话静音
     * @param mute
     */
    public weila_muteAllSession(mute: boolean): void {
        this.sessionModule!.muteAllSessions(mute);
    }

    /**
     *
     * @param sessionId
     */
    public weila_isSessionMute(sessionId: string): boolean {
        return Weila_MemDB.getInstance().isSessionMute(sessionId);
    }

    /**
     *
     */
    public weila_isAllSessionMute(): boolean {
        return this.sessionModule!.isAllSessionsMute();
    }

    /**
     * 设置会话指定id的消息为已读，后续比此id旧的id都是旧消息，比此id新的msg都是新消息
     * @param sessionId
     * @param sessionType
     * @param msgId
     */
    public weila_setSessionMsgRead(sessionId: string, sessionType: number, msgId: number): Promise<boolean> {
        return this.sessionModule!.setSessionMsgRead(sessionId, sessionType, msgId);
    }

    /**
     * 存入会话的名称，如果是群则是群名，如果是临时会话，则是改后的名字，如果是服务号，则是客户名
     * @param sessionId
     * @param sessionName
     */
    public weila_setSessionName(sessionId: string, sessionName: string): void {
        Weila_MemDB.getInstance().putSessionName(sessionId, sessionName);
    }

    /**
     * 存入用户的信息，如名字，头像等。为了文字转语音用（可以只写名字，userId和微喇号）
     * @param userInfo
     */
    public weila_setUserInfo(userInfo: WL_IDbUserInfo): void {
        Weila_MemDB.getInstance().putUser(userInfo);
    }
}

export {WeilaCore}
